import os
import re
from uuid import uuid4
from io import BytesIO
from zipfile import ZipFile
from urllib.request import urlopen, HTTPError
from SCons.Errors import UserError


Import("env")


def resolve_godot_download_url(major, minor, patch, extra, platform):
    version = f"{major}.{minor}.{patch}" if patch != 0 else f"{major}.{minor}"
    if extra == "stable":
        return f"https://downloads.tuxfamily.org/godotengine/{version}/Godot_v{version}-{extra}_{platform}.zip"
    else:
        return f"https://downloads.tuxfamily.org/godotengine/{version}/{extra}/Godot_v{version}-{extra}_{platform}.zip"


def resolve_godot_binary_name(major, minor, patch, extra, platform):
    version = f"{major}.{minor}.{patch}" if patch != 0 else f"{major}.{minor}"
    return f"Godot_v{version}-{extra}_{platform}"


SConscript([f"{env['platform']}/SConscript"])
# Platform-dependant variables
assert "bits" in env
assert "godot_binary_download_platform" in env
assert "cpython_build" in env
assert "cpython_build_dir" in env
assert "DIST_SITE_PACKAGES" in env


# Cython modules need to link against libpython.so
env.AppendUnique(CYTHON_COMPILE_DEPS=[env["cpython_build"]])


### Install CPython build into dist ###


# Installing cpython build into dist cannot be simply done by a
# `env.InstallAs("$DIST_PLATFORM", cypthon_build)` rule given it would
# conflict with the rules that install libpythonscript&godot modules.
# To solve this we represent the installation of the build by a virtual target.
cpython_build_install_marker = env.File("cpython_build_installed_in_dist.marker")
env.VirtualTargetCommand(
    marker=cpython_build_install_marker,
    condition=lambda env: os.path.exists(env.Dir("$DIST_PLATFORM").abspath),
    source=env["cpython_build"],  # Note we don't use `cpython_build_dir` !
    action=[Delete("$DIST_PLATFORM"), Copy("$DIST_PLATFORM", env["cpython_build_dir"])],
)


# Replace default Install command to always depend on cpython build install
env.VanillaInstall = env.Install
env.VanillaInstallAs = env.InstallAs


def install(env, target, source):
    out = env.VanillaInstall(target, source)
    env.Depends(out, cpython_build_install_marker)
    return out


def install_as(env, target, source):
    out = env.VanillaInstallAs(target, source)
    env.Depends(out, cpython_build_install_marker)
    return out


env.AddMethod(install, "Install")
env.AddMethod(install_as, "InstallAs")


### Godot binary (to run tests) ###


if not env["godot_binary"]:
    godot_download_url = resolve_godot_download_url(
        *env["godot_binary_download_version"], env["godot_binary_download_platform"]
    )
    godot_binary_name = resolve_godot_binary_name(
        *env["godot_binary_download_version"], env["godot_binary_download_platform"]
    )
    env["godot_binary"] = File(godot_binary_name)
    godot_binary_zip_path = env.get("godot_binary_download_zip_path", godot_binary_name)

    def download_and_extract(target, source, env):
        try:
            with urlopen(godot_download_url) as rep:
                zipfile = ZipFile(BytesIO(rep.read()))
        except HTTPError as exc:
            # It seems SCons swallows HTTPError, so we have to wrap it
            raise UserError(exc) from exc
        if godot_binary_zip_path not in zipfile.namelist():
            raise UserError(f"Archive doesn't contain {godot_binary_zip_path}")
        with open(target[0].abspath, "wb") as fd:
            fd.write(zipfile.open(godot_binary_zip_path).read())
        if env["HOST_OS"] != "win32":
            os.chmod(target[0].abspath, 0o755)

    env.Command(
        env["godot_binary"],
        None,
        Action(download_and_extract, f"Download&extract {godot_download_url}"),
    )
    env.NoClean(env["godot_binary"])
